:- module(abc_parser, [parseFile/2,
                   setABC/4,
                   getABC/3]).

:- use_module(library(lists)).
:- use_module(library(assoc)).
:- use_module(avm_objects).

:- yap_flag(unknown,error).
:- yap_flag(single_var_warnings, on).


set_breakpoints :-
	spy(abc_parser:minor_version),
	spy(abc_parser:major_version),
	spy(abc_parser:constant_pool),
	spy(abc_parser:method_count),
	spy(abc_parser:methods),
	spy(abc_parser:metadata_count),
	spy(abc_parser:metadatas),
	spy(abc_parser:class_count),
	spy(abc_parser:instances),
	spy(abc_parser:classes),
	spy(abc_parser:script_count),
	spy(abc_parser:scripts),
	spy(abc_parser:body_count),
	spy(abc_parser:method_bodies).

get_AST(File, ABC) :-
	readFile(File, Bytes),
	abcFile(ABC, Bytes, []),
	init_classes, !.

%:- initialization(init_classes).

% =============================================================================
% #region Initializing the types of the data stored in the ABC file 

init_abc_types :-
	empty_assoc(TypeNames),
	nb_setval(typenames, TypeNames).


add_type_to_typenames(Name, TypeObj) :-
	nb_getval(typenames, TypeNames),
	put_assoc(Name, TypeNames, TypeObj, NewTypeNames),
	nb_setval(typenames, NewTypeNames).


create_type_instance(Name, TypeInstance) :-
	b_getval(typenames, TypeNames),
	get_assoc(Name, TypeNames, TypeObj),
	extend_object(TypeObj,TypeInstance).


add_object_type(Name, Props) :-
	get_base_object(BaseObject),
	subclass_object(BaseObject, Name, Props, TypeObj),
	add_type_to_typenames(Name, TypeObj).


init_classes :-
	init_abc_types,

	format('Initializing the ABC File class objects...~n',[]),

	% The basic object
	% Note: I have deleted the "count" fields".
	% I have also deleted the "method_bodies" field.
	add_object_type('ABCFile', [
	  							minor_version,
	  							major_version,
	  							constant_pool,
	  							methods,
	  							metadatas,
	  							instances,
	  							classes,
	  							scripts
							   ]),
	

	% Constant Pool
	add_object_type('ConstantPool', [
 									integers,
 									uintegers,
 									doubles,
 									strings,
 									namespaces,
 									nssets,
 									multinames
 								   ]),

	add_object_type('StringInfo', [
 								  str
 								 ]),

	add_object_type('NamespaceInfo', [
 									 kind,
 									 name
 									]),

	add_object_type('NSSetInfo', [
 								 namespaces
 								]),

	add_object_type('MultinameInfo', [
 									 kind,
 									 data
 									]),

	add_object_type('ConstQName', [
 								  namespace,
 								  name
 								 ]),

	add_object_type('ConstMultiname', [
 									  namespace,
 									  nsset
 									 ]),


	% Method info
	add_object_type('FullMethodInfo', [
									   has_body,
	 								   return_type,
	 								   param_types,
	 								   name,
	 								   flags,
	 								   options,
	 								   param_names,
									   max_stack,
									   local_count,
									   init_scope_depth,
									   max_scope_depth,
									   code_length,
									   code,
									   exception_count,
									   exceptions,
									   trait_count,
									   traits
	 								  ]),

	add_object_type('OptionInfo', [
 								  option_count,
 								  options
 								 ]),

	add_object_type('OptionDetail', [
 									value,
 									kind
 								   ]),

	add_object_type('ParamInfo', [
 								 param_name
 								]),

	add_object_type('InstructionInfo', [
										label,
										offset,
										next,
										args
									   ]),



	% Metadata info
	add_object_type('MetadataInfo', [
 									name,
 									items
 								   ]),

	add_object_type('ItemInfo', [
 								key,
 								value
 							   ]),


	% Object instances
	add_object_type('InstanceInfo', [
 									name,
 									super_name,
 									flags,
 									protectedNs,
 									interfaces,
 									iinit,
 									traits
 								   ]),


	% Traits
	add_object_type('TraitsInfo', [
 								  name,
 								  kind,
								  flags,
 								  data,
 								  metadatas
 								 ]),

	add_object_type('TraitSlot', [
 								 slot_id,
 								 type_name,
 								 vindex,
 								 vkind
 								]),
								
	add_object_type('TraitClass', [
 								  slot_id,
 								  classi
 								 ]),

	add_object_type('TraitFunction', [
 									 slot_id,
 									 function
 									]),

	add_object_type('TraitMethod', [
 								   disp_id,
 								   method
 								  ]),


	% Class
	add_object_type('ClassInfo', [
								 cinit,
								 traits
								]),
	
	add_object_type('ScriptInfo', [
								  init,
								  traits
								 ]),

	% Exceptions
	add_object_type('ExceptionInfo', [
									 from,
									 to,
									 target,
									 exc_type,
									 var_name
									]),
	
	format('Finished initializing the ABC File class objects~n',[]).


% =============================================================================
% #region Creation of the ABC File object
/*
:- if(false).
makeABCObject(ABC,ABCObject) :-
	ABC =.. [abcFile,
			 MinV, MajV,
			 CPool,
			 MethodCount,
			 Methods,
			 MetadataCount,
			 Metadatas,
			 ClassCount,
			 Instances,
			 Classes,
			 ScriptCount,
			 Scripts,
			 MethodBodyCount,
			 MethodBodies],
	instantiate_object(abc,ABC0),
	makeCPoolObject(CPool, CPoolObject),
	set_fields(ABC0, [minor_version = MinV,
					  major_version = MajV,
					  constant_pool = CPoolObject,
					  method_count = MethodCount], ABCObject).
:-endif.
*/

% TODO: Finish sending the parse data to the object creation code (#56)
abc_object(ABC, ABCObject) :-
	create_type_instance('ABCFile', ABCObject),
	ABC =.. [abc,
			 MinorVersion,
			 MajorVersion,
			 ConstantPool,
			 MethodCount,
			 MethodHeaders,
			 MetadataCount,
			 Metadatas,
			 ClassCount,
			 Instances,
			 Classes,
			 ScriptCount,
			 Scripts,
			 MethodBodyCount,
			 MethodBodies
			],

	set_properties(ABCObject, [minor_version - MinorVersion,
							   major_version - MajorVersion]),
	constant_pool_object(ConstantPool, CPObject),
	set_property(ABCObject, constant_pool, CPObject),
	method_objects(ABCObject, MethodCount, MethodHeaders, 
				   MethodBodyCount, MethodBodies, MethodObjectList),
	set_property(ABCObject, methods, MethodObjectList),
	metadata_objects(Metadatas, CPObject, MetadataObjectList),
	set_property(ABCObject, metdadatas, MetadataObjectList),
	instance_objects(Instances, ABCObject, InstanceObjectList),
	set_property(ABCObject, instances, InstanceObjectList),
	class_info_objects(Classes, ABCObject, ClassObjects),
	set_property(ABCObject, classes, ClassObjects),
	script_info_objects(Scripts, ABCObject, ScriptObjects),
	set_property(ABCObject, scripts, ScriptObjects),
	set_traits_method_objects(ABCObject, MethodBodies).


set_traits_method_objects(ABCObject, [MethodBody|Methodbodies]) :-
	set_traits_for_method(ABCObject, MethodBody),
	set_traits_method_objects(ABCObject, Methodbodies).
set_traits_method_objects(_ABCObject, []).

set_traits_for_method(ABCObject, MethodBody) :-
	MethodBody =.. [method_body, 
					MethodNum, 
					_MaxStack, 
					_LocalCount, 
					_InitScopeDepth, 
					_MaxScopeDepth, 
					_CodeLength,
					_Code,
					_ExceptionCount, 
					_Exceptions, 
					_traitCount, 
					Traits],
	abc_data_reference(ABCObject, methods, MethodNum, MethodObject),
	traitinfo_objects(ABCObject, Traits, TraitObjects),
	set_property(MethodObject, traits, TraitObjects).
	



% Helper functions for setting & getting information from an ABC Object.	
abc_data_reference_list(ABCObject, Field, [Idx|Idxs], [Obj|Objs]) :-
	abc_data_reference(ABCObject, Field, Idx, Obj),
	abc_data_reference_list(ABCObject, Field, Idxs, Objs).
abc_data_reference_list(_ABCObject, _Field, [], []).


abc_data_reference(ABCObject, Field, Idx, Obj) :-
	get_property(ABCObject, Field, Objs),
	nth0(Idx, Objs, Obj).

% ============================================================================= 
% #region Constant Pool Creation

constant_pool_object(ConstantPool, CPObject) :-
	create_type_instance('ConstantPool', CPObject),
	ConstantPool =.. [cpoolinfo,
				IntCount, Ints,
				UIntCount, UInts,
				DoubleCount, Doubles,
				StringCount, Strings,
				NamespaceCount, Namespaces,
				NamespaceSetCount, NamespaceSets,
				MultinameCount, Multinames
			   ],
	set_integers(CPObject, IntCount, Ints),
	set_uintegers(CPObject, UIntCount, UInts),
	set_doubles(CPObject, DoubleCount, Doubles),
	set_strings(CPObject, StringCount, Strings),
	set_namespaces(CPObject, NamespaceCount, Namespaces),
	set_nssets(CPObject, NamespaceSetCount, NamespaceSets),
	set_multinames(CPObject, MultinameCount, Multinames).


set_integers(CPObject, IntCount, IntList) :-
	set_basic_value(CPObject, integers, IntCount, [special|IntList]).


set_uintegers(CPObject, UIntCount, UIntList) :-
	set_basic_value(CPObject, uintegers, UIntCount, [special|UIntList]).


set_doubles(CPObject, DoubleCount, DoubleList) :-
	set_basic_value(CPObject, doubles, DoubleCount, [special|DoubleList]).


set_strings(CPObject, StringCount, StringList) :-
	correct_cp_length(strings, StringCount, StringList),

	% We only care about the strings themselves; the characters and the
	% lengths we can compute on the fly (and in general shouldn't be
	% necessary in our analysis), so we don't need to store them.
	string_info_objects(StringList, OnlyStrings),
	set_property(CPObject, strings, [special|OnlyStrings]).


string_info_objects([Str | Strs], [StrObject | StrObjects]) :-
	correct_string(Str),
	arg(3, Str, OnlyS),
	create_type_instance('StringInfo', StrObject),
	set_property(StrObject, str, OnlyS),
	string_info_objects(Strs, StrObjects).

string_info_objects([], []).


correct_string(string_info(Len, Chars, S)) :-
	(atom_chars(S, Chars) ->
		(length(Chars, Len) ->
			true
		;
			length(Chars, BadLen),
			format('ERROR: expecting string of length ~i, received ~a, length ~i~n',
				[Len, S, BadLen]),
			abort
		)
	;
		atom_chars(S, BadChars),
		format('ERROR: codes list ~w does not match with received string ~a, codes ~w~n',
			[Chars, S, BadChars]),
		abort
	).
		

set_namespaces(CPObject, NSCount, NSList) :-
	correct_cp_length(namespaces, NSCount, NSList),
	namespace_objects(CPObject, NSList, NSObjects),
	set_property(CPObject, namespaces, [special|NSObjects]).


namespace_objects(CPObject, [NS | NSs], [NSObject | NSObjects]) :-
	arg(1, NS, NSKind),
	valid_namespace_kind(NSKind),
	arg(2, NS, NSNameIdx),
	cp_data_reference(CPObject, strings, NSNameIdx, StrObj),
	create_type_instance('NamespaceInfo', NSObject),
	set_properties(NSObject, [kind - NSKind, name - StrObj]),
	namespace_objects(CPObject, NSs, NSObjects).

namespace_objects(_CPObject, [], []).


valid_namespace_kind(NSKind) :-
	ValidKinds = [
				  'CONSTANT_Namespace',
				  'CONSTANT_PackageNamespace',
				  'CONSTANT_PackageInternalNs',
				  'CONSTANT_ProtectedNamespace',
				  'CONSTANT_ExplicitNamespace',
				  'CONSTANT_StaticProtectedNs',
				  'CONSTANT_PrivateNs'
				 ],
	(member(NSKind, ValidKinds) ->
		true
	;
		format('ERROR: Namespace kind ~w is not a valid kind~n', [NSKind]),
		abort
	).


set_nssets(CPObject, NSSetCount, NSSetList) :-
	correct_cp_length(nssets, NSSetCount, NSSetList),
	nsset_objects(CPObject, NSSetList, NSSetObjects),
	set_property(CPObject, nssets, [special|NSSetObjects]).


nsset_objects(CPObject, [NSSet | NSSets], [NSSetObject | NSSetObjects]) :-
	arg(1, NSSet, NSSetLen),
	arg(2, NSSet, NSSetList),
	(length(NSSetList, NSSetLen) ->
		cp_data_reference_list(CPObject, namespaces, NSSetList, NamespaceList),
		create_type_instance('NSSetInfo', NSSetObject),
		set_property(NSSetObject, namespaces, NamespaceList),
		nsset_objects(CPObject, NSSets, NSSetObjects)
	;
		length(NSSetList, NSSetLenBad),
		format('ERROR: nsset has ~i elements, count was ~i~n', [NSSetLenBad, NSSetLen]),
		abort
	).

nsset_objects(_CPObject, [], []).


set_multinames(CPObject, MNCount, MNList) :-
	correct_cp_length(multinames, MNCount, MNList),
	multiname_objects(CPObject, MNList, MNObjects),
	set_property(CPObject, multinames, [special|MNObjects]).


multiname_objects(CPObject, [MN | MNs], [MNObject | MNObjects]) :-
	arg(1, MN, MNKind),
	valid_multiname_kind(MNKind),
	arg(2, MN, MNData),
	multiname_data_object(CPObject, MNData, MNDataObject),
	create_type_instance('MultinameInfo', MNObject),
	set_properties(MNObject, [kind - MNKind, data - MNDataObject]),
	multiname_objects(CPObject, MNs, MNObjects).

multiname_objects(_CPObject, [], []).


% Just trivially succeed for now
% TODO: Add actual checking (#58)
valid_multiname_kind(_).


% TODO: Finish implementing the other kinds of multinames (#58)
multiname_data_object(CPObject, qname(NS, Name), QNameObject) :-
	create_type_instance('ConstQName', QNameObject),
	cp_data_reference(CPObject, namespaces, NS, NSObj),
	cp_data_reference(CPObject, strings, Name, NameObj),
	set_properties(QNameObject, [namespace - NSObj, name - NameObj]).

multiname_data_object(CPObject, multiname(Name, NSSet), MultinameObject) :-
	create_type_instance('ConstMultiname', MultinameObject),
	cp_data_reference(CPObject, strings, Name, NameObj),
	cp_data_reference(CPObject, nssets, NSSet, NSSetObj),
	set_properties(MultinameObject, [name - NameObj, nsset - NSSetObj]).


cp_data_reference_list(CPObject, Field, [Idx | Idxs], [Obj | Objs]) :-
	cp_data_reference(CPObject, Field, Idx, Obj),
	cp_data_reference_list(CPObject, Field, Idxs, Objs).

cp_data_reference_list(_CPObject, _Field, [], []).


cp_data_reference(CPObject, Field, Idx, Obj) :-
	get_property(CPObject, Field, Objs),
	nth0(Idx, Objs, Obj).
	

set_basic_value(CPObject, Type, Count, List) :-
	correct_cp_length(Type, Count, List),
	set_property(CPObject, Type, List).

correct_cp_length(_Type,_Count,_List) :- true.

% This check breaks the build    % BUG
/*
correct_cp_length(Type, Count1, List) :-
	% According to the spec, the IntCount should be the size of the integer
	% array + 1 (for the 0 value)
	Count is Count1 - 1,
	(length(List, Count) ->
		true
	;
		length(List, CountBad),
		format('ERROR: ~a count = ~i, received ~i values~n', 
			[Type, Count, CountBad]),
		abort
	).
*/
%------------------------------------- METADATA -----------------------------
metadata_objects([Metadata|Metadatas], CPObject, [MetadataObject|MetadataObjectList]) :-
	metadata_object(Metadata, CPObject, MetadataObject),
	metdata_objects(Metadatas, CPObject, MetadataObjectList).
metadata_objects([], _CPObject, []).

% need to insert check to make sure name field is not zero.
metadata_object(Metadata, CPObject, MetadataObject) :-
	create_type_instance('MetadataInfo', MetadataObject),
	Metadata =.. [metadata, Name, _ItemCount, Items],
	cp_data_reference(CPObject, strings, Name, StringObj),
	set_property(MetadataObject, name, StringObj),
	set_metadata_items(MetadataObject, CPObject, Items).

set_metadata_items(MetadataObject, CPObject, ItemList) :-
	item_objects(ItemList, CPObject, ItemObjects),
	set_property(MetadataObject, items, ItemObjects).

item_objects([Item|Items], CPObject, [ItemObject|ItemObjects]) :-
	item_object(Item, CPObject, ItemObject),
	item_objects(Items, CPObject, ItemObjects).
item_objects([], _CPObject, []).

item_object(Item, CPObject, ItemObject) :-
	create_type_instance('ItemInfo', ItemObject),
	Item =.. [p, Key, Value],
	cp_data_reference(CPObject, strings, Key, StringObj1),
	set_property(ItemObject, key, StringObj1),
	cp_data_reference(CPObject, strings, Value, StringObj2),
	set_property(ItemObject, value, StringObj2).


%--------------------------------------- INSTANCES --------------------------

instance_objects([Instance|Instances], ABCObject, [InstanceObject|InstanceObjects]) :-
	instance_object(Instance, ABCObject, InstanceObject),
	instance_objects(Instances, ABCObject, InstanceObjects).
instance_objects([], _ABCObject, []).

%Error Handling Cases to be taken care of:
%1) The name field must be a QName.
%2) The super_name field having a value of zero indicates that this class has
%no base class.
%3) In the flags field, only expected bits must be set, others must be zero.
%4) The protectedNs field is present only if the flags field has a bit set to
%protectedNs. 
%5) Check the intrf_count and trait_counts.
instance_object(Instance, ABCObject, InstanceObject) :-
	create_type_instance('InstanceInfo', InstanceObject),
	Instance =.. [instance, 
					Name, 
					SuperName, 
					Flags, 
					ProtectedNs,
				   	_InterfaceCount,
				   	Interfaces, 
					Iinit, 
					_TraitCount,
				   	Traits],
	get_property(ABCObject, constant_pool, CPObject),
	cp_data_reference(CPObject, multinames, Name, NameMultinameObject),
	set_property(InstanceObject, name, NameMultinameObject),
	cp_data_reference(CPObject, multinames, SuperName, SuperNameMultinameObject),
	set_property(InstanceObject, super_name, SuperNameMultinameObject),
	set_property(InstanceObject, flags, Flags),
	cp_data_reference(CPObject, namespaces, ProtectedNs, NameSpaceObject),
	set_property(InstanceObject, protectedNs, NameSpaceObject),
	cp_data_reference_list(CPObject, multinames, Interfaces, MultinameObjects),
	set_property(InstanceObject, interfaces, MultinameObjects),
	abc_data_reference(ABCObject, methods, Iinit, MethodObj),
	%	nth0(Iinit, MethodObjectList, MethodObj),
	set_property(InstanceObject, iinit, MethodObj),
	traitinfo_objects(ABCObject, Traits, TraitObjects),
	set_property(InstanceObject, traits, TraitObjects).
	
traitinfo_objects(ABCObject,				
					[TraitInfo|TraitInfos], 
					[TraitInfoObject|TraitInfoObjects]) :-
	traits_info_object(TraitInfo, ABCObject, TraitInfoObject),
	traitinfo_objects(ABCObject, TraitInfos, TraitInfoObjects).
traitinfo_objects(_ABCObject, [], []).


%--------------------------------------- TRAITS -----------------------
	
traits_info_object(TraitInfo, ABCObject, TraitsInfoObject) :-
	create_type_instance('TraitsInfo', TraitsInfoObject),
	TraitInfo =.. [trait, Name, Kind, Flags, Data, _MC, Metadatas],
	get_property(ABCObject, constant_pool, CPObject),
	cp_data_reference(CPObject, multinames, Name, NameMultinameObject),
	set_property(TraitsInfoObject, name, NameMultinameObject),
	set_property(TraitsInfoObject, kind, Kind),
	set_property(TraitsInfoObject, flags, Flags),
	set_property(TraitsInfoObject, data, Data),
	get_metadata_objects(ABCObject, Metadatas, MetadataObjects),
	set_property(TraitsInfoObject, metadatas, MetadataObjects).

get_metadata_objects(ABCObject,						
						[MetaDataIndex|MetaDataIndices], 
						[MetadataObject|MetadataObjects]) :-	
	abc_data_reference(ABCObject, metadatas, MetaDataIndex, MetadataObject),
	%nth0(MetaDataIndex, MetadataObjectList, MetadataObject),
	get_metadata_objects(ABCObject, MetaDataIndices, MetadataObjects).	
get_metadata_objects(_ABCObject, [], []).	
%------------------------------------- TRAIT SLOT --------------------

%Error Checking cases to take care of in the future:
%1) if ID is 0, AVM is supposed to assign a position.
%2) A value of zero for the TypeName field indicates that the type is any
%type.
%3) A value of zero for vindex indicates that vkind is empty. 
trait_slot_object(TraitSlot, CPObject, TraitSlotObject) :-
	create_type_instance('TraitSlot', TraitSlotObject),
	TraitSlot =.. [trait_slot, ID, TypeName, VIndex, VKind],
	set_property(TraitSlotObject, slot_id, ID),
	cp_data_reference(CPObject, multinames, TypeName, TypeNameMO),
	set_property(TraitSlotObject, type_name, TypeNameMO),
	set_property(TraitSlotObject, vkind, VKind),
	get_ref_for_vindex(VKind, VIndex, CPObject, Obj),
	set_property(TraitSlotObject, vindex, Obj).

get_ref_for_vindex('CONSTANT_Int', Idx, CPObject, Obj) :-
	cp_data_refernce(CPObject, integers, Idx, Obj).
get_ref_for_vindex('CONSTANT_UInt', Idx, CPObject, Obj) :- 
	cp_data_reference(CPObject, uintegers, Idx, Obj).
get_ref_for_vindex('CONSTANT_Double', Idx, CPObject, Obj) :-
	cp_data_reference(CPObject, doubles, Idx, Obj).
get_ref_for_vindex('CONSTANT_Utf8', Idx, CPObject, Obj) :-
	cp_data_reference(CPObject, strings, Idx, Obj).
get_ref_for_vindex('CONSTANT_True', _Idx, _CPObject, true).
get_ref_for_vindex('CONSTANT_False', _Idx, _CPObject, false).
get_ref_for_vindex('CONSTANT_Null', _Idx, _CPObject, null).
get_ref_for_vindex('CONSTANT_Undefined', _Idx, _CPObject, undefined).
get_ref_for_vindex('CONSTANT_Namespace', Idx, CPObject, Obj) :-
	cp_data_reference(CPObject, namespaces, Idx, Obj).
get_ref_for_vindex('CONSTANT_PackageNamespace', Idx, CPObject, Obj) :-
	cp_data_reference(CPObject, namespaces, Idx, Obj).
get_ref_for_vindex('CONSTANT_PackageInternalNs', Idx, CPObject, Obj) :-
	cp_data_reference(CPObject, namespaces, Idx, Obj).
get_ref_for_vindex('CONSTANT_ProtectedNamespace', Idx, CPObject, Obj) :-
	cp_data_reference(CPObject, namespaces, Idx, Obj).
get_ref_for_vindex('CONSTANT_ExplicitNamespace', Idx, CPObject, Obj) :-
	cp_data_reference(CPObject, namespaces, Idx, Obj).
get_ref_for_vindex('CONSTANT_StaticProtectedNs', Idx, CPObject, Obj) :-
	cp_data_reference(CPObject, namespaces, Idx, Obj).
get_ref_for_vindex('CONSTANT_PrivateNs', Idx, CPObject, Obj) :-
	cp_data_reference(CPObject, namespaces, Idx, Obj).

%----------------------------------- TRAIT CLASS ---------------------

trait_class_object(TraitClass, ABCObject, TraitClassObject) :-
	create_type_instance('TraitClass', TraitClassObject),
	TraitClass =.. [trait_class, ID, Class],
	set_property(TraitClassObject, slot_id, ID),
	abc_data_reference(ABCObject, classes, Class, Obj),
	set_property(TraitClassObject, classi, Obj).

%----------------------------------- TRAIT FUNCTION ------------------

trait_function_object(TraitFunction, ABCObject, TraitFunctionObject) :-
	create_type_instance('TraitFunction', TraitFunctionObject),
	TraitFunction =.. [trait_function, ID, Function],
	set_property(TraitFunctionObject, slot_id, ID),
	abc_data_reference(ABCObject, methods, Function, Obj),
	set_property(TraitFunctionObject, function, Obj).

%----------------------------------- TRAIT METHOD --------------------

trait_method_object(TraitMethod, ABCObject, TraitMethodObject) :-
	create_type_instance('TraitMethod', TraitMethodObject),
	TraitMethod =.. [trait_method, DispID, Method],
	set_property(TraitMethodObject, disp_id, DispID),
	abc_data_reference(ABCObject, methods, Method, Obj),
	set_property(TraitMethodObject, method, Obj).

%----------------------------------- CLASS INFO -----------------------

class_info_objects([Class|Classes], 
					ABCObject,
					[ClassObject|ClassObjects]) :-
	class_info_object(Class, 
						ABCObject,
						ClassObject),
	class_info_objects(Classes, 
						ABCObject,
						ClassObjects).
class_info_objects([], _ABCObject, []).

class_info_object(ClassInfo, 
					ABCObject,
					ClassInfoObject) :-
	create_type_instance('ClassInfo', ClassInfoObject),
	ClassInfo =.. [class_info, CInit, _TC, Traits],
	abc_data_reference(ABCObject, methods, CInit, MethodObject),
	%	nth0(CInit, MethodObjectList, MethodObject),
	set_property(ClassInfoObject, cinit, MethodObject),
	traitinfo_objects(ABCObject, Traits, TraitObjects),
	set_property(ClassInfoObject, traits, TraitObjects).

%----------------------------------- SCRIPT INFO ----------------------
script_info_objects([Script|Scripts], 
					ABCObject,
					[ScriptObject|ScriptObjects]) :-
	script_info_object(Script, 
						ABCObject,
						ScriptObject),
	script_info_objects(Scripts, 
						ABCObject,
						ScriptObjects).
script_info_objects([], _ABCObject, []).

script_info_object(ScriptInfo, ABCObject, ScriptInfoObject) :-
	create_type_instance('ScriptInfo', ScriptInfoObject),
	ScriptInfo =.. [script_info, Init, _TC, Traits],
	abc_data_reference(ABCObject, methods, Init, MethodObject),
	%nth0(Init, MethodObjectList, MethodObject),
	set_property(ScriptInfoObject, init, MethodObject),
	traitinfo_objects(ABCObject, Traits, TraitObjects),
	set_property(ScriptInfoObject, traits, TraitObjects).



% ============================================================================
% #region Method objects

/**
 * Given the method headers and bodies, create the `FullMethodInfo` objects
 * for the methods in the ABC file.
 *
 * We start by creating a `FullMethodInfo` object for every header, then
 * linking up the bodies with those objects that correspond to the bodies'
 * associated headers. Every header has an associated `FullMethodInfo` object;
 * check `FullMethodInfo.has_body` to determine whether the object has
 * associated method body information.
 *
 * See `method_header_objects` and `methods_with_bodies` for more information
 * about the processing done.
 *
 * We return the list of these `FullMethodInfo` objects so that the caller can
 * set the `ABCFile.methods` accordingly.
 *
 * Note that we cannot set the traits info here, since that information has
 * not been added to the ABCFile. This should be done after constructing the
 * method and traits objects as a separate step. 
 * !! DO NOT FORGET TO DO THIS !!
 *
 * @param +ABCObject The ABC Object constructed so far; used to link up the
 *					 data in the methods to things in the constant pool, etc.
 * @param +MethodHeaderCount, +MethodHeaders The method header info
 * @param +MethodBodyCount, +MethodBodies The method body info
 * @param -MethodObjectList The list of `FullMethodInfo` objects, returned
 *							after constructing the objects from the header 
 *							and body data.					 
 */
method_objects(ABCObject, 
			   MethodHeaderCount, MethodHeaders,
			   MethodBodyCount, MethodBodies,
			   MethodObjectList
			  ) :-
	% Make sure the lengths are correct
	correct_data_length('method headers', MethodHeaderCount, MethodHeaders),
	correct_data_length('method bodies', MethodBodyCount, MethodBodies),
	get_property(ABCObject, constant_pool, CPObject),

	% Process all the method headers
	method_header_objects(CPObject, MethodHeaders, MethodObjectList),

	% Process each of the bodies, pairing it with the Method object that we've
	% already created.
	%
	% Note that we (may) need the full ABC Object, not just the constant pool.
	methods_with_bodies(ABCObject, MethodObjectList, MethodBodies).


/**
 * Take each method header and create a `FullMethodInfo` object for it, then
 * pass it to `process_header_info` to handle populating the object with data.
 *
 * We default the `has_body` to false; this will be overridden when
 * processing the method bodies, if our method object has a body in the ABC
 * file.
 *
 * We return the list of `FullMethodInfo` objects as the last parameter.
 */
method_header_objects(CPObject, [MethodHeader | MethodHeaders], [MethodObject | MethodObjects]) :-
	% Create the method object, and initially set `has_body` to `false`. We'll
	% reset it to `true` if we find a body to match it.
	create_type_instance('FullMethodInfo', MethodObject),
	set_property(MethodObject, has_body, false),

	% Process the header info
	process_header_info(CPObject, MethodHeader, MethodObject),
	method_header_objects(CPObject, MethodHeaders, MethodObjects).

method_header_objects(_CPObject, [], []).


/**
 * Process the method header information.
 *
 * We link up the parameter and return types with their multinames, the method
 * name with the string constant.
 *
 * NOTE: We do not handle options, flags, or parameter names yet.
 *
 * @param +CPObject The constant pool object created in a prior step.
 * @param +HeaderFtor The method_info functor created by the parser to hold
 *					  the method header information.
 * @param +MethodObject The `FullMethodInfo` object created for this header.
 *					    Note that this parameter should be ground - we do not
 *					    return a new `FullMethodInfo` object, but rather 
 *					    modify the values for its fields as stored in the 
 *					    heap.
 */
process_header_info(CPObject, HeaderFtor, MethodObject) :-
	HeaderFtor =.. [method_info,
					ParamCount,
					ReturnType,
					ParamTypes,
					Name,
					Flags,
					Options,
					ParamNames
				   ],

	% Match up the param_types
	correct_data_length('parameter count', ParamCount, ParamTypes),
	cp_data_reference_list(CPObject, multinames, ParamTypes, ParamTypeObjs),
	set_property(MethodObject, param_types, ParamTypeObjs),
	
	% Match up the return_type
	cp_data_reference(CPObject, multinames, ReturnType, ReturnTypeObj),
	set_property(MethodObject, return_type, ReturnTypeObj),

	% Match up the name
	cp_data_reference(CPObject, strings, Name, NameStr),
	set_property(MethodObject, name, NameStr),

	% Handle the flags
	% TODO: We don't handle these for now; we just assign them as-is
	process_header_flags(CPObject, Flags, MethodObject),

	% Handle the options
	% TODO: Actually handle the options - for now we just assume there are
	% none
	process_header_options(CPObject, Options, MethodObject),

	
	% Handle the param names
	% TODO: Actually handle the param names - we assume there are none at the
	% moment. Also note that they are not used by the AVM itself, so we may
	% not need them at all.
	process_header_param_names(CPObject, ParamNames, MethodObject).


/**
 * Process the flags field for the method header
 *
 * TODO: THIS CURRENTLY JUST SETS THE FIELD TO THE FLAGS VALUE (#59)
 *  * I do not know if any additional things need to be done here, this may be
 *    fine as-is.
 */
process_header_flags(_CPObject, Flags, MethodObject) :-
	set_property(MethodObject, flags, Flags).


/**
 * Process the options field for the method header.
 *
 * TODO: THIS CURRENTLY SETS THE FIELD TO NONE. (#59)
 */
process_header_options(_CPObject, option_info(none), MethodObject) :-
	set_property(MethodObject, options, none).


/**
 * Process the parameter names for the method header.
 *
 * TODO: THIS CURRENTLY SETS THE FIELD TO NONE (#59)
 */
process_header_param_names(_CPObject, param_info(none), MethodObject) :-
	set_property(MethodObject, param_names, none).


/**
 * Process the method bodies supplied in the ABC file. We first link up the
 * body information with the `FullMethodInfo` object specified by the `method`
 * field in the docs (see AVM2 Overview, S.4.11) and set the `has_body` field
 * to true. We then call `process_body_info` to do the heavy lifting.
 *
 * Note that if we attempt to attach a body to a `FullMethodInfo` object that
 * already has its `has_body` field set to true, we throw an error and abort,
 * since (from my understanding) there should only be one header associated
 * with each body.
 *
 * @param +ABCObject The `ABCFile` object so we can link with other data
 * @param MethodObjects The list of `FullMethodInfo` objects. Note that we
 *						will not modify the parameter, but will use their
 *						references to update the fields for the associated
 *						object.
 * @param +MethodBodies A list of the method_body functors returned by the
 *						parser containing the method body data.
 */
methods_with_bodies(ABCObject, MethodObjects, [MethodBody | MethodBodies]) :-
	MethodBody =.. [method_body, MethodNum | _MethodBodyInfo],
	nth0(MethodNum, MethodObjects, MethodObject),
	(get_property(MethodObject, has_body, true) ->
		format('Attempting to match 2 method bodies to the header ~i~n',
				[MethodNum]),
		abort
	;
		true
	),
	set_property(MethodObject, has_body, true),
	process_body_info(ABCObject, MethodObject, MethodBody),

	methods_with_bodies(ABCObject, MethodObjects, MethodBodies).

methods_with_bodies(_ABCObject, _MethodObjects, []).


/**
 * Process the `method_body` functor information provided by the parser,
 * attaching the information to the supplied `FullMethodInfo` object.
 *
 * We verify the correct lengths of everything, since we won't be storing this
 * information in the actual object.
 *
 * Most of the other fields can be set trivially. However, there are three
 * main things to note:
 *		1) We currently do not handle exceptions (this is a TODO - see #54).
 *		2) We cannot handle linking up the Traits information at this stage,
 *		   since the `TraitsInfo` objects haven't been created yet. 
 *		   * We may want to reorganize the code so this is handled more
 *		     elegantly?
 *		   * This is a TODO: See #55
 *		3) We will go through and process the code, updating references and
 *		   links as necessary.
 *		   * There are some instructions that refer to the method objects.
 *		     Since these have not been associated with the ABCFile yet, we
 *		     will have to figure out how to handle these. There may also be
 *		     additional opcodes that reference data (such as traits) that have
 *		     not been created yet. I have not seen any in my perusal of the
 *		     opcodes list, but I'm not certain there aren't any.
 * 
 * @param +ABCObject The `ABCFile` object that stores other data for linkage
 *					 purposes.
 * @param +BodyFtor The `method_body` functor received from the parser that
 *					contains the method body data.
 * @param +MethodObject A reference to the `FullMethodInfo` object being
 *						created. This should be ground, as we modify the
 *						object in the global heap via this reference.
 */
process_body_info(ABCObject, MethodObject, BodyFtor) :-
	BodyFtor =.. [method_body,
				  _MethodNum,
				  MaxStack,
				  LocalCount,
				  InitScopeDepth,
				  MaxScopeDepth,
				  _CodeLength,
				  Code,
				  ExceptionCount,
				  Exceptions,
				  TraitCount,
				  Traits
				 ],
	
	% Verify the data lengths
	% Commenting out: CodeLength is in *bytes*, not instructions, so this
	% obviously goes blewy.
	% correct_data_length('code', CodeLength, Code),
	
	correct_data_length('exceptions', ExceptionCount, Exceptions),
	correct_data_length('traits', TraitCount, Traits),
	
	% Set the properties for the method object
	% 
	% NOTE: We must save traits till later, since we need traits to process the
	% method, but we need the method to parse the traits. So we should get the
	% method objects first, then create the trait objects, then process the
	% `traits` field afterwards.
	%
	% TODO: We aren't handling exceptions here; we'll have to add the handling
	% for this eventually. (see #54)
	set_properties(MethodObject, [max_stack - MaxStack,
								  local_count - LocalCount,
								  init_scope_depth - InitScopeDepth,
								  max_scope_depth - MaxScopeDepth,
								  exceptions - Exceptions,
								  traits - Traits
								 ]),
	
	% Now handle the code of the method
	process_code(ABCObject, Code, ProcessedCode),
	set_property(MethodObject, code, ProcessedCode).


/**
 * Processes the code field of the method body.
 *
 * See `initial_instruction_objects` for how we construct the initial
 * `InstructionInfo` objects. See `process_instruction_list` for how we
 * process the actual instructions.
 *
 * @param +ABCObject The `ABCFile` object that stores other data for linkage
 *					 purposes.
 * @param +CodeList The list of Offset - Opcode pairs returned by the parser
 *					representing the opcodes of this method.
 * @param -ProcessedCode The `InstructionInfo` objects created as a
 *						 result of processing the code.
 */
process_code(ABCObject, CodeList, ProcessedCode) :-
	% Create the `InstructionInfo` objects and populate them with the basic
	% data.
	%
	% Note: this returns Offset - `InstructionInfo` object pairs. We need this
	% to pair up the offsets with the instructions in an association list.
	initial_instruction_objects(CodeList, InstrObjList),
	
	% Create an association list between the offsets and the Instructions at
	% those offsets. This way it's easier to compute jumps.
	%
	% NOTE: We are assuming that there are no inter-method jumps - all jumps
	% are performed within the current method, and every method starts at
	% offset 0.
	list_to_assoc(InstrObjList, OffsetAssoc),
	
	process_instruction_list(ABCObject, InstrObjList, OffsetAssoc, ProcessedCode).


/**
 * Creates the `InstructionInfo` objects for the supplied Offset - Opcode
 * pairs. Returns a list of Offset - `InstructionInfo` object pairs, so that
 * the Offsets can be used to key the `InstructionInfo` objects in an
 * association list (used for jumps).
 *
 * We set the basic data of:
 *		* `label` - The actual opcode instruction, sans arguments, if any
 *		* `offset` - The memory offset of the instruction
 *		* `args` - Any arguments to the opcode ([] if none)
 *
 * +Instrs A list of Offset (int) - Opcode pairs
 * -InstructionObjects A list of Offset - `InstructionInfo` object ref pairs.
 */
initial_instruction_objects([O - Instr | Instrs], 
							[O - InstructionObject | InstructionObjects]) :-
	create_type_instance('InstructionInfo', InstructionObject),
	
	% NOTE: Even for simple terms (i.e., non-functors), `=..` will unify with
	% a list consising of `Term | []`. This means we can use it in all cases,
	% even for instructions that do not have arguments.
	Instr =.. [Opcode | Args],
	set_properties(InstructionObject, [label - Opcode, offset - O, args - Args]),
	initial_instruction_objects(Instrs, InstructionObjects).

initial_instruction_objects([], []).


/**
 * Processes the list of Offset - `InstructionInfo` object pairs. Sets the
 * fields `next` field to the next instruction (`undefined` if there is none
 * in the list), and processes the instruction based on its opcode, linking up
 * any data with the rest of the ABCObject if need be.
 *
 * Note that this also performs the double-duty of stipping off the offset
 * tag for each opcode. We needed it when creating the association list (see
 * `process_code` and `initial_instruction_objects`, but we don't need it any
 * more. Even for jump instructions, we obtain the offset from the `offset`
 * field of the `InstructionInfo` object.
 *  * Would it be clearer / cleaner to perform the stripping step separately?
 *
 * @param +ABCObject The `ABCFile` object that stores other data for linkage
 *					 purposes.
 * @param +InstructionObjects The list of Offset - `InstructionInfo` pairs.
 * @param +OffsetAssoc The association list from memory offsets to the
 *					   `InstructionInfo` object for that location.
 * @param -Instructions The actual `InstructionInfo` objects (basically the
 *						same as the `InstructionObjects` list, except the
 *						offset key has been stripped.
 */
process_instruction_list(ABCObject, 
						 [_O1 - InstructionObject1, 
						  O2 - InstructionObject2 | InstrObjRest], 
	 					 OffsetAssoc,
						 [InstructionObject1 | Instructions]) :-
	set_property(InstructionObject1, next, InstructionObject2),
	get_property(InstructionObject1, label, Label),
	get_property(InstructionObject1, args, Args),
	OpFunctor =.. [Label|Args],
	process_instruction(ABCObject, InstructionObject1, OpFunctor, OffsetAssoc),
	process_instruction_list(ABCObject, [O2-InstructionObject2 | InstrObjRest],
								OffsetAssoc, Instructions).

process_instruction_list(ABCObject,
						 [_O - InstructionObject],
						 OffsetAssoc,
						 [InstructionObject]) :-
	% Set the next instruction object to be `undefined`. This makes the most
	% sense - there is no "next" instruction, so we should always have a
	% return at this point. It doesn't make sense to set it to something like
	% `halted`, since we may not be done with the program, only returning from
	% a method.
	set_property(InstructionObject, next, undefined),
	get_property(InstructionObject, label, Label),
	get_property(InstructionObject, args, Args),
	OpFunctor =.. [Label|Args],
	process_instruction(ABCObject, InstructionObject, OpFunctor, OffsetAssoc).

% TODO: Figure out whether this is the correct semantics - should all bodies
% have at least N instructions? If so, then throw an error here - we should
% never reach here unless we're provided an empty opcodes list at the start.
% process_instruction_list(_ABCObject, [], _OffsetAssoc, []).


/**
 * Process each individual instruction, linking up the arguments with constant
 * pool or other ABCObject data as necessary.
 *
 * @param +ABCObject The `ABCFile` object that stores other data for linkage
 *					 purposes.
 * @param +InstructionObject The `InstructionInfo` object reference to update.
 *							 Note that this should be ground, since we are
 *							 only taking in the reference and modifying the
 *							 object via the global heap.
 * @param +Opcode The opcode functor to process into an instruction. This does
 *				  not have any offset information - the offset is already set
 *				  in the InstructionObject (as `offset`).
 * @param +OffsetAssoc The association list between memory offsets and
 *					   `InstructionInfo` objects. Only really used for the
 *					   jump instructions, ignored everywhere else (so far).
 */

% Jump instructions
% Note that we have no need for the `ABCFile` object here
process_instruction(_ABCObject, InstructionObject, Opcode, OffsetAssoc) :-
	% The list of instructions that can cause us to jump
	% TODO: Add lookupswitch here?
	JumpList = [
				ifeq(_),
				iffalse(_),
				ifge(_),
				ifgt(_),
				ifle(_),
				iflt(_),
				ifnge(_),
				ifngt(_),
				ifnle(_),
				ifnlt(_),
				ifne(_),
				ifstricteq(_),
				ifstrictneq(_),
				iftrue(_),
				jump(_)
			   ],
	% Is our opcode a jump?
	member(Opcode, JumpList),

	% It is (otherwise we would have failed).
	% Get the target offset and find the actual instruction.
	arg(1, Opcode, Dist),
	get_property(InstructionObject, next, Next), % FIXME: THIS IS PROBABLY BAD
	get_property(Next, offset, Offset),
	Target is Offset + Dist,
	% TODO: Add actual error checking here, rather than just failing silently;
	% see #61.
	get_assoc(Target, OffsetAssoc, TargetInstruction),
	set_property(InstructionObject, jumplabel, TargetInstruction).

% Property instructions
process_instruction(ABCObject, InstructionObject, Opcode, _OffsetAssoc) :-
	PropertyOpcodeList = [
						  findproperty(_),
						  findpropstrict(_),
						  getlex(_),
						  getproperty(_),
						  getsuper(_),
						  initproperty(_),
						  setproperty(_),
						  setsuper(_)
						 ],
	
	member(Opcode, PropertyOpcodeList),
	arg(1, Opcode, Idx),
	assign_multiname(ABCObject, InstructionObject, Idx, []).

% Methods
process_instruction(ABCObject, InstructionObject, Opcode, _OffsetAssoc) :-
	MethodCallOpcodes = [
						 callproperty(_, _),
						 callproplex(_, _),
						 callpropvoid(_, _),
						 callsuper(_, _),
						 callsupervoid(_, _),
						 constructprop(_, _)
						],
	member(Opcode, MethodCallOpcodes),
	Opcode =.. [_, Idx, ArgC],
	assign_multiname(ABCObject, InstructionObject, Idx, [ArgC]).

% TODO: Figure out how to do this _while_ we're making the method objects.
% Maybe come back later and do it? 
% See #60
% For now, we comment out the singleton variables.
process_instruction(_ABCObject, _InstructionObject, 
					callstatic(_Idx, _ArgC), _OffsetAssoc) :-
		fail.

% Push-data instructions
process_instruction(ABCObject, InstructionObject, pushdouble(Idx), _OffsetAssoc) :-
	process_push_instruction(ABCObject, InstructionObject, doubles, Idx).

process_instruction(ABCObject, InstructionObject, pushint(Idx), _OffsetAssoc) :-
	process_push_instruction(ABCObject, InstructionObject, integers, Idx).

process_instruction(ABCObject, InstructionObject, pushuint(Idx), _OffsetAssoc) :-
	process_push_instruction(ABCObject, InstructionObject, uintegers, Idx).

process_instruction(ABCObject, InstructionObject, pushstring(Idx), _OffsetAssoc) :-
	process_push_instruction(ABCObject, InstructionObject, strings, Idx).

% Typing
process_instruction(ABCObject, InstructionObject, Opcode, _OffsetAssoc) :-
	TypeOpcodes = [
				   coerce(_),
				   istype(_)
				  ],

	member(Opcode, TypeOpcodes),
	arg(1, Opcode, Idx),
	assign_multiname(ABCObject, InstructionObject, Idx, []).

% Catch-all for opcodes that don't need additional linking information
process_instruction(_ABCObject, _InstructionObject, Opcode, _OffsetAssoc) :-
	JumpList = [
				ifeq(_),
				iffalse(_),
				ifge(_),
				ifgt(_),
				ifle(_),
				iflt(_),
				ifnge(_),
				ifngt(_),
				ifnle(_),
				ifnlt(_),
				ifne(_),
				ifstricteq(_),
				ifstrictneq(_),
				iftrue(_),
				jump(_)
			   ],
	PropertyOpcodeList = [
						  findproperty(_),
						  findpropstrict(_),
						  getlex(_),
						  getproperty(_),
						  getsuper(_),
						  initproperty(_),
						  setproperty(_),
						  setsuper(_)
						 ],
	MethodCallOpcodes = [
						 callproperty(_, _),
						 callproplex(_, _),
						 callpropvoid(_, _),
						 callsuper(_, _),
						 callsupervoid(_, _),
						 constructprop(_, _)
						],
	PushOpcodes = [
				   pushdouble(_),
				   pushint(_),
				   pushuint(_),
				   pushstring(_)
				  ],
	TypeOpcodes = [
				   coerce(_),
				   istype(_)
				  ],
	OtherOpcodes = [
					callstatic(_, _),
					lookupswitch(_, _, _, _)
				   ],

	append(JumpList, PropertyOpcodeList, Opcodes1),
	append(Opcodes1, MethodCallOpcodes, Opcodes2),
	append(Opcodes2, PushOpcodes, Opcodes3),
	append(Opcodes3, TypeOpcodes, Opcodes4),
	append(Opcodes4, OtherOpcodes, HandledOpcodesList),

	% If it's been handled before, then fail. If it hasn't then there's
	% nothing more we have to do.
	\+ member(Opcode, HandledOpcodesList).


/**
 * Assign the multiname object at index `Idx` (in the multiname pool) as the
 * `args` field in the `InstructionInfo` object.
 *
 * @param +ABCObject The `ABCFile` object that stores other data for linkage
 *					 purposes.
 * @param +InstructionObject The `InstructionInfo` object reference to update.
 *							 Note that this should be ground, since we are
 *							 only taking in the reference and modifying the
 *							 object via the global heap.
 * @param +Idx The index into the multiname constant pool to grab.
 * @param +AdditionalArgs Since some opcodes have additional args after the
 *						  index, we can supply those here to have them added
 *						  afterwards. 
 *						    * Thus far, all opcodes use the first argument to
 *						      specify the index, so this works. We may need to
 *						      change this later if some opcodes do not follow
 *						      this convention.
 */
assign_multiname(ABCObject, InstructionObject, Idx, AdditionalArgs) :-
	get_property(ABCObject, constant_pool, CPObject),
	get_property(CPObject, multinames, MultinameList),
	nth0(Idx, MultinameList, MultinameObj),
	set_property(InstructionObject, args, [MultinameObj | AdditionalArgs]).


/**
 * A helper predicate to make it easier to set the args of a pushx opcode to
 * be the reference to the constant pool we are pushing.
 *
 * @param +ABCObject The `ABCFile` object that stores other data for linkage
 *					 purposes.
 * @param +InstructionObject The `InstructionInfo` object reference to update.
 *							 Note that this should be ground, since we are
 *							 only taking in the reference and modifying the
 *							 object via the global heap.
 * @param +Type The type of constant to obtain. Should match an entry in the
 *				`ConstantPool` object (contained in `ABCObject`).
 *				   * TODO: Do sanity checking here.
 * @param +Idx The index of the multiname entry to grab
 *				 * TODO: Do sanity checking here.
 */
process_push_instruction(ABCObject, InstructionObject, Type, Idx) :-
	get_property(ABCObject, constant_pool, CPObject),
	get_property(CPObject, Type, Entries),
	nth0(Idx, Entries, Entry),
	set_property(InstructionObject, args, [Entry]).


/**
 * Takes a list of data entries and verifies that the count is correct for the
 * list, outputting an error message and aborting if not.
 *
 * @param +Type The type of list we are checking; used only for better output.
 * @param +Count The expected length of the list
 * @param +List The list to check.
 */
correct_data_length(Type, Count, List) :-
	(length(List, Count) ->
		true
	;
		length(List, BadCount),
		format('ERROR: ~a length ~i, expected ~i~n',
			[Type, BadCount, Count]),
		abort
	).




% ============================================================================
% #region ABC Format Parser

parseFile(File,ABCObject) :- parseFile(File, ABCObject, _).
parseFile(File, ABCObject, Bytes, ABC) :- readFile(File,Bytes), abcFile(ABC,Bytes,[]), init_classes, abc_object(ABC,ABCObject), !.

test_output(Bytes, F) :-
		abc_parser:parseFile('TargetLoop.abc', _, Bytes, T), 
		break, 
		
		%trace, 
		abc_parser:abcFile(T, F, []),
		write_file('out_TargetLoop.abc', F).


readFile(File,ByteList) :-
	open(File, read, Stream, [type(binary)]),
	readBytes(ByteList,Stream),
	close(Stream), !.

readBytes([C|Rest],Stream) :-
	get_byte(Stream,C),
	C \= -1,
	!,
	readBytes(Rest,Stream).
readBytes([],Stream) :-
	get_byte(Stream, C),
	C == -1.


write_file(File, ByteList) :-
	open(File, write, Stream, [type(binary)]),
	write_bytes(ByteList, Stream),
	close(Stream),
	!.

write_bytes([C | Rest], Stream) :-
	put_byte(Stream, C),
	!,
	write_bytes(Rest, Stream).

write_bytes([], _Stream).



check(FlagsByte,Mask) :- ((FlagsByte /\ Mask) > 0).

% abcFile(u16 minor_version,
%         u16 major_version,
%         cpool_info constant_pool,
%         u30 method_count,
%         method_info method[method_count],
%         u30 metadata_count,
%         metadata_info metadata[metadata_count],
%         u30 class_count,
%         instance_info instance[class_count],
%         class_info class[class_count],
%         u30 script_count,
%         script_info script[script_count],
%         u30 method_body_count,
%         method_body_info method_body[method_body_count])
%         
newABC(abc(tt,tt,tt,tt,tt,tt,tt,tt,tt,tt,tt,tt,tt,tt)).

setABC(minor_version,OldABC,Value,NewABC) :-
	OldABC = abc(_V0,V1,V2,V3,V4,V5,V6,V7,V8,V9,V10,V11,V12,V13),
	NewABC = abc(Value,V1,V2,V3,V4,V5,V6,V7,V8,V9,V10,V11,V12,V13).
setABC(major_version,OldABC,Value,NewABC) :-
	OldABC = abc(V0,_V1,V2,V3,V4,V5,V6,V7,V8,V9,V10,V11,V12,V13),
	NewABC = abc(V0,Value,V2,V3,V4,V5,V6,V7,V8,V9,V10,V11,V12,V13).
setABC(constant_pool,OldABC,Value,NewABC) :-
	OldABC = abc(V0,V1,_V2,V3,V4,V5,V6,V7,V8,V9,V10,V11,V12,V13),
	NewABC = abc(V0,V1,Value,V3,V4,V5,V6,V7,V8,V9,V10,V11,V12,V13).
setABC(method_count,OldABC,Value,NewABC) :-
	OldABC = abc(V0,V1,V2,_V3,V4,V5,V6,V7,V8,V9,V10,V11,V12,V13),
	NewABC = abc(V0,V1,V2,Value,V4,V5,V6,V7,V8,V9,V10,V11,V12,V13).
setABC(methods,OldABC,Value,NewABC) :-
	OldABC = abc(V0,V1,V2,V3,_V4,V5,V6,V7,V8,V9,V10,V11,V12,V13),
	NewABC = abc(V0,V1,V2,V3,Value,V5,V6,V7,V8,V9,V10,V11,V12,V13).
setABC(metadata_count,OldABC,Value,NewABC) :-
	OldABC = abc(V0,V1,V2,V3,V4,_V5,V6,V7,V8,V9,V10,V11,V12,V13),
	NewABC = abc(V0,V1,V2,V3,V4,Value,V6,V7,V8,V9,V10,V11,V12,V13).
setABC(metadatas,OldABC,Value,NewABC) :-
	OldABC = abc(V0,V1,V2,V3,V4,V5,_V6,V7,V8,V9,V10,V11,V12,V13),
	NewABC = abc(V0,V1,V2,V3,V4,V5,Value,V7,V8,V9,V10,V11,V12,V13).
setABC(class_count,OldABC,Value,NewABC) :-
	OldABC = abc(V0,V1,V2,V3,V4,V5,V6,_V7,V8,V9,V10,V11,V12,V13),
	NewABC = abc(V0,V1,V2,V3,V4,V5,V6,Value,V8,V9,V10,V11,V12,V13).
setABC(instances,OldABC,Value,NewABC) :-
	OldABC = abc(V0,V1,V2,V3,V4,V5,V6,V7,_V8,V9,V10,V11,V12,V13),
	NewABC = abc(V0,V1,V2,V3,V4,V5,V6,V7,Value,V9,V10,V11,V12,V13).
setABC(classes,OldABC,Value,NewABC) :-
	OldABC = abc(V0,V1,V2,V3,V4,V5,V6,V7,V8,_V9,V10,V11,V12,V13),
	NewABC = abc(V0,V1,V2,V3,V4,V5,V6,V7,V8,Value,V10,V11,V12,V13).
setABC(script_count,OldABC,Value,NewABC) :-
	OldABC = abc(V0,V1,V2,V3,V4,V5,V6,V7,V8,V9,_V10,V11,V12,V13),
	NewABC = abc(V0,V1,V2,V3,V4,V5,V6,V7,V8,V9,Value,V11,V12,V13).
setABC(scripts,OldABC,Value,NewABC) :-
	OldABC = abc(V0,V1,V2,V3,V4,V5,V6,V7,V8,V9,V10,_V11,V12,V13),
	NewABC = abc(V0,V1,V2,V3,V4,V5,V6,V7,V8,V9,V10,Value,V12,V13).
setABC(body_count,OldABC,Value,NewABC) :-
	OldABC = abc(V0,V1,V2,V3,V4,V5,V6,V7,V8,V9,V10,V11,_V12,V13),
	NewABC = abc(V0,V1,V2,V3,V4,V5,V6,V7,V8,V9,V10,V11,Value,V13).
setABC(method_bodies,OldABC,Value,NewABC) :-
	OldABC = abc(V0,V1,V2,V3,V4,V5,V6,V7,V8,V9,V10,V11,V12,_V13),
	NewABC = abc(V0,V1,V2,V3,V4,V5,V6,V7,V8,V9,V10,V11,V12,Value).


getABC(minor_version,ABC,Value) :-
	ABC = abc(Value,_V1,_V2,_V3,_V4,_V5,_V6,_V7,_V8,_V9,_V10,_V11,_V12,_V13).
getABC(major_version,ABC,Value) :-
	ABC = abc(_V0,Value,_V2,_V3,_V4,_V5,_V6,_V7,_V8,_V9,_V10,_V11,_V12,_V13).
getABC(constant_pool,ABC,Value) :-
	ABC = abc(_V0,_V1,Value,_V3,_V4,_V5,_V6,_V7,_V8,_V9,_V10,_V11,_V12,_V13).
getABC(method_count,ABC,Value) :-
	ABC = abc(_V0,_V1,_V2,Value,_V4,_V5,_V6,_V7,_V8,_V9,_V10,_V11,_V12,_V13).
getABC(methods,ABC,Value) :-
	ABC = abc(_V0,_V1,_V2,_V3,Value,_V5,_V6,_V7,_V8,_V9,_V10,_V11,_V12,_V13).
getABC(metadata_count,ABC,Value) :-
	ABC = abc(_V0,_V1,_V2,_V3,_V4,Value,_V6,_V7,_V8,_V9,_V10,_V11,_V12,_V13).
getABC(metadatas,ABC,Value) :-
	ABC = abc(_V0,_V1,_V2,_V3,_V4,_V5,Value,_V7,_V8,_V9,_V10,_V11,_V12,_V13).
getABC(class_count,ABC,Value) :-
	ABC = abc(_V0,_V1,_V2,_V3,_V4,_V5,_V6,Value,_V8,_V9,_V10,_V11,_V12,_V13).
getABC(instances,ABC,Value) :-
	ABC = abc(_V0,_V1,_V2,_V3,_V4,_V5,_V6,_V7,Value,_V9,_V10,_V11,_V12,_V13).
getABC(classes,ABC,Value) :-
	ABC = abc(_V0,_V1,_V2,_V3,_V4,_V5,_V6,_V7,_V8,Value,_V10,_V11,_V12,_V13).
getABC(script_count,ABC,Value) :-
	ABC = abc(_V0,_V1,_V2,_V3,_V4,_V5,_V6,_V7,_V8,_V9,Value,_V11,_V12,_V13).
getABC(scripts,ABC,Value) :-
	ABC = abc(_V0,_V1,_V2,_V3,_V4,_V5,_V6,_V7,_V8,_V9,_V10,Value,_V12,_V13).
getABC(body_count,ABC,Value) :-
	ABC = abc(_V0,_V1,_V2,_V3,_V4,_V5,_V6,_V7,_V8,_V9,_V10,_V11,Value,_V13).
getABC(method_bodies,ABC,Value) :-
	ABC = abc(_V0,_V1,_V2,_V3,_V4,_V5,_V6,_V7,_V8,_V9,_V10,_V11,_V12,Value).

abcFile(ABC) -->
		 {ABC = abc(MinV,MajV,
			CP,
			MC,Methods,
			MetaC,Metas,
			CC,Instances,Classes,
			SC,Scripts,
			BC,MB)},

		 minor_version(MinV),!,
	         major_version(MajV),!,
		 % {open('methods_crazy.txt',write,MStream),
		 %tell(MStream)},
		 %{writeln('here?')},
		 constant_pool(CP),!,
		 method_count(MC),!,
		 methods(Methods,MC),!,
		 metadata_count(MetaC),!,
		 metadatas(Metas,MetaC),!,
		 class_count(CC),!,
		 instances(Instances,CC),!,

		 classes(Classes,CC),!,
		 script_count(SC),!,
		 scripts(Scripts,SC),!,		 
		 body_count(BC),!,
		 {writeln('cominghere?')},
		 % {trace},
		 method_bodies(MB,BC),!.
	 %		 {told}.

minor_version(MinV) --> u16(MinV).
major_version(MajV) --> u16(MajV).

constant_pool(CP) --> cpool_info(CP).

method_count(MC) --> u30(MC).
methods([],0) --> [].
methods([M|R],MC) --> {MC>0, NMC is MC-1}, method_info(M), methods(R,NMC).

metadata_count(MC) --> u30(MC).

metadatas([],0) --> [].
metadatas([M|R],MetaC) --> {MetaC>0, NMC is MetaC-1}, metadata_info(M), metadatas(R,NMC).

class_count(CC) --> u30(CC).

instances([],0) --> [].
instances([I|R],CC) --> {CC>0, NCC is CC-1}, instance_info(I), instances(R,NCC).

classes([],0) --> [].
classes([C|R],CC) --> {CC>0, NCC is CC-1}, class_info(C), classes(R,NCC).

script_count(SC) --> u30(SC).

scripts([],0) --> [].
scripts([S|R],SC) --> {SC>0, NSC is SC-1}, script_info(S), scripts(R,NSC).

body_count(BC) --> u30(BC).

method_bodies([],0) --> [].
method_bodies([M|R],BC) --> 
	{BC>0, NBC is BC-1}, 
	method_body_info(M),
	%	 {
	%	writeln(M),
	%	writeln('\n\n')},
	method_bodies(R,NBC).

u8(I) --> [I],!.

u16(I) --> [C1], [C2], {u16Int(C1,C2,I)},!.

u16Int(C1,C2,I) :-
	nonvar(C1),
	nonvar(C2),
	I is C1 \/ (C2 << 8).
u16Int(C1,C2,I) :-
	var(C1),
	var(C2),
	C2 is ((I >> 8) /\ 255),
	C1 is (I /\ 255).

s24(I) --> [C1], [C2], [C3], {s24Int(C1,C2,C3,I)},!.

s24Int(C1,C2,C3,I) :-
	nonvar(C1), nonvar(C2), nonvar(C3),
	IsNeg is C3 /\ 128,
	(IsNeg == 128 -> I1 is -1 << 24; I1 is 0),
	I is I1 + (C3 << 16) + (C2 << 8) + C1.
s24Int(C1,C2,C3,I) :-
	var(C1), var(C2), var(C3),
	C1 is (I /\ 255),
	C2 is ((I >> 8) /\ 255),
	C3 is ((I >> 16) /\ 255).

u30(I) --> 
	{\+ ground(I)}, 
	varLength(Bytes), 
	{u30Int(Bytes,0,0,0,I)},
	!.

u30(I) -->
	{ground(I)},
	{u30Int_rev(Bytes, I)},
	varLength(Bytes),
	!.


u30(I,L) --> 
	{\+ ground(I)}, 
	varLength(Bytes), 
	{
		u30Int(Bytes,0,0,0,I), 
		length(Bytes,L)
	},
	!.

u30(I, L) -->
	{ground(I)},

	% TODO: Print error message if Bytes is not of len L.
	{u30Int_rev(Bytes, I), length(Bytes, L)},
	varLength(Bytes).


/**
 * u30Int(ByteList, Counter, Shift, Current, Final)
 *
 * Takes a u30 encoded as a sequence of bytes and converts it to the integer
 * equivalent.
 *
 * @param ByteList The list of bytes read from the file
 * @param Counter The counter - 0..4
 * @param Shift The amount to shift the current byte by
 * @param Current The current amount seen so far
 * @param Final The final value returned.
 */
u30Int([],_N,_S,I,I).
u30Int([Cur|Rest],N,S,P,I) :-
	N < 4,
	Byte is Cur /\ 127,		% Take the low 7 bits
	Add is Byte << S,		% Shift it the proper places
	Next is P + Add,		% Add to the current value
	NextN is N + 1,			% Inc the counter
	NextS is S + 7,			% Update the next shift amount
	u30Int(Rest,NextN,NextS,Next,I).
u30Int([Cur|_Rest],4,S,P,I) :-
	Byte is Cur /\ 127,
	Add is (Byte /\ 3) << S,
	I is P + Add.

% TODO: Error check the value to output?
u30Int_rev([Curr], I) :-
	I2 is I >> 7,
	I2 =:= 0,
	Byte is I /\ 127,
	Curr is Byte \/ (0 << 7).
u30Int_rev([Curr | Rest], I) :-
	I2 is I >> 7,
	I2 > 0,
	Byte is I /\ 127,
	Curr is Byte \/ (1 << 7),
	u30Int_rev(Rest, I2).


u32(I) --> 
	{\+ ground(I)},
	varLength(Bytes), {u32Int(Bytes,0,0,0,I)},!.

u32(I) -->
	{ground(I)},
	{u32Int_rev(Bytes, I)},
	varLength(Bytes), !.

u32Int([],_N,_S,I,I).
u32Int([Cur|Rest],N,S,P,I) :-
	N < 4,
	Byte is Cur /\ 127,
	Add is Byte << S,
	Next is P + Add,
	NextN is N+1,
	NextS is S + 7,
	u32Int(Rest,NextN,NextS,Next,I).
u32Int([Cur|_Rest],4,S,P,I) :-
	Byte is Cur /\ 127,
	Add is (Byte /\ 15) << S,
	I is P + Add.

% TODO: Error check the value to output?
u32Int_rev([Curr], I) :-
	I2 is I >> 7,
	I2 =:= 0,
	Byte is I /\ 127,
	Curr is Byte \/ (0 << 7).
u32Int_rev([Curr | Rest], I) :-
	I2 is I >> 7,
	I2 > 0,
	Byte is I /\ 127,
	Curr is Byte \/ (1 << 7),
	u32Int_rev(Rest, I2).


s32(I) --> u32(I),!.

d64(d([B0,B1,B2,B3,B4,B5,B6,B7])) --> [B0],[B1],[B2],[B3],[B4],[B5],[B6],[B7],!.

varLength(Bytes) --> varLengthInt(Bytes,4).

varLengthInt([Byte],0) --> [Byte].
varLengthInt([Byte],N) --> {N > 0}, finalByte(Byte).
varLengthInt([Byte|Rest],N) --> {N > 0, P is N-1}, nonfinalByte(Byte), varLengthInt(Rest,P).

finalByte(Byte) --> [Byte], {Flag is Byte /\ 128, Flag == 0}.

nonfinalByte(Byte) --> [Byte], {Flag is Byte /\ 128, Flag >= 0}.

% cpoolinfo(int_count, integers, uint_count, uintegers, double_count, doubles, string_count, strings, namespace_count, namespaces, ns_set_count, ns_sets, multiname_count, multinames)
print_arraycount(Array, Count, String) :-
       write(String),
       write('Count is:'),
       write(Count),
       write('\n'),
       write('printing array:\n'),
       help_print_arrays(Array),
       write('---------------------------\n').
	
help_print_arrays([A|As]) :- write(A), write('\n'), help_print_arrays(As).
help_print_arrays([]) :- write('').

print_ns([],_).
print_ns([Ns|NsRest],Strings) :-
	Ns =.. [namespace_info, Kind, Name],
	nth0(Name,Strings,String),
	write(Ns),
	writeln(String),
	print_ns(NsRest,Strings).

cpool_info(cpoolinfo(IntCount,Integers,UIntCount,UIntegers,DoubleCount,Doubles,StringCount,Strings,NamespaceCount,Namespaces,NSSetCount,NSSets,MultinameCount,Multinames)) --> 
	u30(IntCount),
	integers(Integers,IntCount),
	
	u30(UIntCount),
	uintegers(UIntegers,UIntCount),
	u30(DoubleCount),
	doubles(Doubles,DoubleCount),
	
	u30(StringCount),
	strings(Strings,StringCount),
	%	{break},
	u30(NamespaceCount),
	namespaces(Namespaces,NamespaceCount),
	
	u30(NSSetCount),
	nssets(NSSets,NSSetCount),
	u30(MultinameCount),
	multinames(Multinames,MultinameCount).
	
% Index is wierd because int_count, etc. are one bigger than they should be
integers([],0) --> [].
integers([],1) --> [].
integers([I|R],Count) --> {Count > 1, NC is Count-1}, s32(I), integers(R,NC).

uintegers([],0) --> [].
uintegers([],1) --> [].
uintegers([U|R],Count) --> {Count > 1, NC is Count-1}, u32(U), uintegers(R,NC).

doubles([],0) --> [].
doubles([],1) --> [].
doubles([D|R],Count) --> {Count > 1, NC is Count-1}, d64(D), doubles(R,NC).

strings([],0) --> [].
strings([],1) --> [].
strings([S|R],Count) --> 
{Count > 1, NC is Count-1}, string_info(S),strings(R,NC).

namespaces([],0) --> [].
namespaces([],1) --> [].
namespaces([N|R],Count) --> {Count > 1, NC is Count-1}, namespace_info(N), namespaces(R,NC).

nssets([],0) --> [].
nssets([],1) --> [].
nssets([N|R],Count) --> {Count > 1, NC is Count-1}, nsset_info(N), nssets(R,NC).

multinames([],0) --> [].
multinames([],1) --> [].
multinames([M|R],Count) --> 
	{Count > 1, NC is Count-1}, 
	multiname_info(M), 
	multinames(R,NC).

% string_info(size,chars)
string_info(string_info(Size,Chars,String)) --> 
	u30(Size), 
	string(Chars,Size), 
	{atom_codes(String,Chars)}.

string([],0) --> [].
string([H|R],N) --> {N>0, N1 is N-1}, [H], string(R,N1).

% namespace_info(Kind,Name)
namespace_info(namespace_info(Kind,Name)) --> namespace_kind(Kind), u30(Name).

namespace_kind('CONSTANT_Namespace') --> [0x08].
namespace_kind('CONSTANT_PackageNamespace') --> [0x16].
namespace_kind('CONSTANT_PackageInternalNs') --> [0x17].
namespace_kind('CONSTANT_ProtectedNamespace') --> [0x18].
namespace_kind('CONSTANT_ExplicitNamespace') --> [0x19].
namespace_kind('CONSTANT_StaticProtectedNs') --> [0x1A].
namespace_kind('CONSTANT_PrivateNs') --> [0x05].

% ns_set_info(count,ns_sets)
nsset_info(ns_set_info(Count,Namespaces)) --> u30(Count), namespace_set(Namespaces,Count).

namespace_set([],0) --> [].
namespace_set([H|R],N) --> {N>0, N1 is N-1}, u30(H), namespace_set(R,N1).

% m_info(Kind,Data)
multiname_info(m_info('CONSTANT_QName',Data)) --> [0x07], qname(Data).
multiname_info(m_info('CONSTANT_QNameA',Data)) --> [0x0D], qname(Data).
multiname_info(m_info('CONSTANT_RTQName',Data)) --> [0x0F], rtqname(Data).
multiname_info(m_info('CONSTANT_RTQNameA',Data)) --> [0x10], rtqname(Data).
multiname_info(m_info('CONSTANT_RTQNameL')) --> [0x11].
multiname_info(m_info('CONSTANT_RTQNameLA')) --> [0x12].
multiname_info(m_info('CONSTANT_Multiname',Data)) --> [0x09], multiname(Data).
multiname_info(m_info('CONSTANT_MultinameA',Data)) --> [0x0E], multiname(Data).
multiname_info(m_info('CONSTANT_MultinameL',Data)) --> [0x1B], multinameL(Data).
multiname_info(m_info('CONSTANT_MultinameLA',Data)) --> [0x1C], multinameL(Data).
multiname_info(m_info('CONSTANT_TypeName',Data)) --> [0x1D], typename(Data).

qname(qname(NS,Name)) --> u30(NS), u30(Name).

rtqname(rtqname(Name)) --> u30(Name).

multiname(multiname(Name,NSSet)) --> u30(Name), u30(NSSet).

multinameL(multinameL(NSSet)) --> u30(NSSet).

typename(typename(Name,ParamsLength,Params)) --> u30(Name),u30(ParamsLength),params(Params,ParamsLength).

params([],0) --> [].
params([M|R],Count) --> 
	{Count > 0, NC is Count-1}, 
	u30(M),
	params(R,NC).


% method_info(param_count,return_type,param_types,name,flags,options,param_names)
method_info(method_info(PC,RT,PT,N,F,O,PN)) --> 
	u30(PC), 
	u30(RT), 
	param_types(PT,PC),
	u30(N),
	method_flags(F),
	option_info(O,F),
	param_info(PN,PC,F).

param_types([],0) --> [].
param_types([T|R],N) --> {N>0, NN is N-1}, u30(T), param_types(R,NN).

method_flags(Flags) --> 
	{\+ ground(Flags)},
	[FlagsByte], 
	{decode_method_flags(FlagsByte,Flags)}.

method_flags(Flags) -->
	{ground(Flags)},
	{encode_method_flags(FlagsByte, Flags)},
	[FlagsByte].

% TODO: Fix this so it's cleaner and reversible
% I'd think this should be doable -- Brian
decode_method_flags(FlagsByte,Flags) :-
	need_args(FlagsByte,[],Flags1),
	need_act(FlagsByte,Flags1,Flags2),
	need_rest(FlagsByte,Flags2,Flags3),
	has_opt(FlagsByte,Flags3,Flags4),
	set_dxns(FlagsByte,Flags4,Flags5),
	has_param_names(FlagsByte,Flags5,Flags).

need_args(FlagsByte,List,['NEED_ARGUMENTS'|List]) :- check(FlagsByte,0x01).
need_args(FlagsByte,List,List) :- \+check(FlagsByte,0x01).

need_act(FlagsByte,List,['NEED_ACTIVATION'|List]) :- check(FlagsByte,0x02).
need_act(FlagsByte,List,List) :- \+check(FlagsByte,0x02).

need_rest(FlagsByte,List,['NEED_REST'|List]) :- check(FlagsByte,0x04).
need_rest(FlagsByte,List,List) :- \+check(FlagsByte,0x04).

has_opt(FlagsByte,List,['HAS_OPTIONAL'|List]) :- check(FlagsByte,0x08).
has_opt(FlagsByte,List,List) :- \+check(FlagsByte,0x08).

set_dxns(FlagsByte,List,['SET_DXNS'|List]) :- check(FlagsByte,0x40).
set_dxns(FlagsByte,List,List) :- \+check(FlagsByte,0x40).

has_param_names(FlagsByte,List,['HAS_PARAM_NAMES'|List]) :- check(FlagsByte,0x80).
has_param_names(FlagsByte,List,List) :- \+check(FlagsByte,0x80).


encode_method_flags(FlagsByte, ['NEED_ARGUMENTS' | List]) :-
	encode_method_flags(FlagsByte1, List),
	FlagsByte is FlagsByte1 \/ 0x01.

encode_method_flags(FlagsByte, ['NEED_ACTIVATION' | List]) :-
	encode_method_flags(FlagsByte1, List),
	FlagsByte is FlagsByte1 \/ 0x02.

encode_method_flags(FlagsByte, ['NEED_REST' | List]) :-
	encode_method_flags(FlagsByte1, List),
	FlagsByte is FlagsByte1 \/ 0x04.

encode_method_flags(FlagsByte, ['HAS_OPTIONAL' | List]) :-
	encode_method_flags(FlagsByte1, List),
	FlagsByte is FlagsByte1 \/ 0x08.

encode_method_flags(FlagsByte, ['SET_DXNS' | List]) :-
	encode_method_flags(FlagsByte1, List),
	FlagsByte is FlagsByte1 \/ 0x40.

encode_method_flags(FlagsByte, ['HAS_PARAM_NAMES' | List]) :-
	encode_method_flags(FlagsByte1, List),
	FlagsByte is FlagsByte1 \/ 0x80.

encode_method_flags(0, []).



option_info(option_info(Count,Details),F) --> {member('HAS_OPTIONAL',F)}, u30(Count), option_details(Details,Count).
option_info(option_info(none),F) --> {\+member('HAS_OPTIONAL',F)}, [].

option_details([],0) --> [].
option_details([O|R],N) --> {N>0, NN is N-1}, option_detail(O), option_details(R,NN).

option_detail(option(Kind,Val)) --> u30(Val), constant_kind(Kind).

constant_kind('CONSTANT_Int') --> [0x03].
constant_kind('CONSTANT_UInt') --> [0x04].
constant_kind('CONSTANT_Double') --> [0x06].
constant_kind('CONSTANT_Utf8') --> [0x01].
constant_kind('CONSTANT_True') --> [0x0B].
constant_kind('CONSTANT_False') --> [0x0A].
constant_kind('CONSTANT_Null') --> [0x0C].
constant_kind('CONSTANT_Undefined') --> [0x00].
constant_kind('CONSTANT_Namespace') --> [0x08].
constant_kind('CONSTANT_PackageNamespace') --> [0x16].
constant_kind('CONSTANT_PackageInternalNs') --> [0x17].
constant_kind('CONSTANT_ProtectedNamespace') --> [0x18].
constant_kind('CONSTANT_ExplicitNamespace') --> [0x19].
constant_kind('CONSTANT_StaticProtectedNs') --> [0x1A].
constant_kind('CONSTANT_PrivateNs') --> [0x05].

param_info(PN,C,F) --> {member('HAS_PARAM_NAMES',F)}, param_names(PN,C).
param_info(param_info(none),_C,F) --> {\+member('HAS_PARAM_NAMES',F)}.

param_names([],0) --> [].
param_names([P|R],N) --> {N>0, NN is N-1}, u30(P), param_names(R,NN).

% metadata(name,count,items)

metadata_info(metadata(Name,Count,Items)) --> u30(Name), u30(Count), metadata_items(Items,Count).

metadata_items([],0) --> [].
metadata_items([I|R],N) --> {N>0, NN is N-1}, metadata_item(I), metadata_items(R,NN).

metadata_item(p(Key,Value)) --> u30(Key), u30(Value).

% instance(name,super_name,flags,protectedNs,interface_count,interfaces,iinit,trait_count,traits)

instance_info(instance(Name,SuperName,Flags,ProtectedNs,InterfaceCount,Interfaces,Iinit,TraitCount,Traits)) -->
	u30(Name),
	u30(SuperName),
	instance_flags(Flags),
	protectedNs(ProtectedNs,Flags),
	u30(InterfaceCount),
	interfaces(Interfaces,InterfaceCount),
	u30(Iinit),
	u30(TraitCount),
	traits(Traits,TraitCount).

instance_flags(Flags) --> 
	{\+ ground(Flags)},
	[FlagsByte], 
	{decode_instance_flags(FlagsByte,Flags)}.

instance_flags(Flags) -->
	{ground(Flags)},
	{encode_instance_flags(FlagsByte, Flags)},
	[FlagsByte].

decode_instance_flags(FlagsByte,Flags) :-
	class_sealed(FlagsByte,[],Flags1),
	class_final(FlagsByte,Flags1,Flags2),
	class_interface(FlagsByte,Flags2,Flags3),
	class_protectedNs(FlagsByte,Flags3,Flags).

class_sealed(FlagsByte,List,['CONSTANT_ClassSealed'|List]) :- check(FlagsByte,0x01).
class_sealed(FlagsByte,List,List) :- \+check(FlagsByte,0x01).

class_final(FlagsByte,List,['CONSTANT_ClassFinal'|List]) :- check(FlagsByte,0x02).
class_final(FlagsByte,List,List) :- \+check(FlagsByte,0x02).

class_interface(FlagsByte,List,['CONSTANT_ClassInterface'|List]) :- check(FlagsByte,0x04).
class_interface(FlagsByte,List,List) :- \+check(FlagsByte,0x04).

class_protectedNs(FlagsByte,List,['CONSTANT_ClassProtectedNs'|List]) :- check(FlagsByte,0x08).
class_protectedNs(FlagsByte,List,List) :- \+check(FlagsByte,0x08).


encode_instance_flags(FlagsByte, ['CONSTANT_ClassSealed'|List]) :-
	encode_instance_flags(FlagsByte1, List),
	FlagsByte is FlagsByte1 \/ 0x01.

encode_instance_flags(FlagsByte, ['CONSTANT_ClassFinal'|List]) :- 
	encode_instance_flags(FlagsByte1, List),
	FlagsByte is FlagsByte1 \/ 0x02.

encode_instance_flags(FlagsByte, ['CONSTANT_ClassInterface'|List]) :- 
	encode_instance_flags(FlagsByte1, List),
	FlagsByte is FlagsByte1 \/ 0x04.

encode_instance_flags(FlagsByte, ['CONSTANT_ClassProtectedNs'|List]) :-
	encode_instance_flags(FlagsByte1, List),
	FlagsByte is FlagsByte1 \/ 0x08.

encode_instance_flags(0, []).

protectedNs(none,F) --> {\+member('CONSTANT_ClassProtectedNs',F)}, [].
protectedNs(P,F) --> {member('CONSTANT_ClassProtectedNs',F)}, u30(P).

interfaces([],0) --> [].
interfaces([I|R],C) --> {C>0, NC is C-1}, u30(I), interfaces(R,NC).

traits([],0) --> [].
traits([T|R],C) --> {C>0, NC is C-1}, trait_info(T), traits(R,NC).

% trait(name,kind,flags,data,metadata_count,metadatas)

trait_info(trait(Name,Kind,Flags,Data,MC,Metadatas)) -->
	u30(Name),
	trait_kind(Kind,Flags),
	trait_data(Data,Kind),
	trait_metadatas(Metadatas,MC,Flags).

trait_kind(Kind,Flags) --> 
	{ \+ ground(Kind), \+ ground(Flags)},
	[Byte], 
	{Low is Byte /\ 0xF, decode_trait_kind(Low,Kind),
	 High is Byte >> 4, decode_trait_flags(High,Flags)}.

trait_kind(Kind, Flags) -->
	{ground(Kind), ground(Flags)},
	{
		% Use decode_trait_kind - it's all facts, so it's reversible.
		decode_trait_kind(Low, Kind),
		encode_trait_flags(High, Flags),
		Byte is Low \/ (High << 4)
	},
	[Byte].

decode_trait_kind(0,'Trait_Slot').
decode_trait_kind(1,'Trait_Method').
decode_trait_kind(2,'Trait_Getter').
decode_trait_kind(3,'Trait_Setter').
decode_trait_kind(4,'Trait_Class').
decode_trait_kind(5,'Trait_Function').
decode_trait_kind(6,'Trait_Const').

decode_trait_flags(Bits,Flags) :-
	trait_attr_final(Bits,[],F1),
	trait_attr_override(Bits,F1,F2),
	trait_attr_metadata(Bits,F2,Flags).

trait_attr_final(Bits,List,['ATTR_Final'|List]) :- check(Bits,0x01).
trait_attr_final(Bits,List,List) :- \+check(Bits,0x01).

trait_attr_override(Bits,List,['ATTR_Override'|List]) :- check(Bits,0x02).
trait_attr_override(Bits,List,List) :- \+check(Bits,0x02).

trait_attr_metadata(Bits,List,['ATTR_Metadata'|List]) :- check(Bits,0x04).
trait_attr_metadata(Bits,List,List) :- \+check(Bits,0x04).

encode_trait_flags(Bits, ['ATTR_Final' | List]) :-
	encode_trait_flags(Bits1, List),
	Bits is Bits1 \/ 0x01.

encode_trait_flags(Bits, ['ATTR_Override' | List]) :-
	encode_trait_flags(Bits1, List),
	Bits is Bits1 \/ 0x02.

encode_trait_flags(Bits, ['ATTR_Metadata' | List]) :-
	encode_trait_flags(Bits1, List),
	Bits is Bits1 \/ 0x04.

encode_trait_flags(0, []).


trait_data(Data,'Trait_Slot') --> trait_slot(Data).
trait_data(Data,'Trait_Const') --> trait_slot(Data).

trait_data(Data,'Trait_Class') --> trait_class(Data).

trait_data(Data,'Trait_Function') --> trait_function(Data).

trait_data(Data,'Trait_Method') --> trait_method(Data).
trait_data(Data,'Trait_Getter') --> trait_method(Data).
trait_data(Data,'Trait_Setter') --> trait_method(Data).

trait_slot(trait_slot(ID,TypeName,0,nil)) --> 
	u30(ID), 
	u30(TypeName),
	u30(0).
trait_slot(trait_slot(ID,TypeName,VIndex,VKind)) -->
	u30(ID),
	u30(TypeName),
	u30(VIndex),
	{VIndex \= 0},
	constant_kind(VKind).

trait_class(trait_class(ID,Class)) --> u30(ID), u30(Class).

trait_function(trait_function(ID,Function)) --> u30(ID), u30(Function).

trait_method(trait_method(DispID,Method)) --> u30(DispID), u30(Method).

trait_metadatas([],0,Flags) --> {\+member('ATTR_Metadata',Flags)}, [].
%trait_metadatas([],0,Flags) --> {member('ATTR_Metadata',Flags), write('first case')}, [].
trait_metadatas(Metadatas,MC,Flags) --> {\+member('ATTR_Metadata',Flags)}, u30(MC), trait_mdatas(Metadatas,MC).
trait_metadatas(Metadatas,MC,Flags) --> {member('ATTR_Metadata', Flags)}, 
	u30(MC), trait_mdatas(Metadatas,MC).

trait_mdatas([],0) --> [].
trait_mdatas([M|R],C) --> {C>0, NC is C-1}, u30(M), trait_mdatas(R,NC).
trait_mdatas([], C) --> {C > 0, write(C), write('trait_mdatas, emptylist'), break}.
trait_mdatas([M|R],0) -->{write('trait_mdatas, count0'), break}.
                           
% class_info(cinit,trait_count,traits)

class_info(class_info(CInit,TC,Traits)) --> u30(CInit), u30(TC), traits(Traits,TC).

% script_info(init,trait_count,traits)

script_info(script_info(Init,TC,Traits)) --> u30(Init), u30(TC), traits(Traits,TC).

% method_body_info(Method,MaxStack,LocalCount,InitScopeDepth,MaxScopeDepth,
%                  CodeLen,Code,ExceptionCount,Exceptions,TraitCount,Traits)

method_body_info(method_body(Method,MS,LC,ISD,MSD,CL,C,EC,E,TC,T)) -->

	u30(Method),
	u30(MS),
	u30(LC),
	u30(ISD),
	u30(MSD),

	u30(CL),
	%{write('Method Num: '), writeln(Method)},
	code(C,0,CL),
	%{writeln(C)},

	u30(EC),
	exceptions(E,EC),
	u30(TC),
	traits(T,TC).

code([],_Offset,0) --> [].
code([Offset - I|R],Offset,C) --> 
	{C>0}, 
	%{writeln(Offset - I)},
	instruction(I,IL), 
	%{writeln(Offset - I)},
	!, 
	{NC is C-IL}, 
	{NewOffset is Offset + IL},
	%{write(NewOffset)}, {write(' - ')}, {write(IL)}, {write(' - ')}, {writeln(C)},
	%format('NewOffset=~w,NC=~w~n',[NewOffset,NC])}, 
	code(R,NewOffset,NC), !.

exceptions([],0) --> [].
exceptions([E|R],C) --> {C>0, NC is C-1}, exception(E), exceptions(R,NC), !.

% exception(from,to,target,exc_type,var_name)

exception(exception(From,To,Target,ExcType,VarName)) --> 
	u30(From),
	u30(To),
	u30(Target),
	u30(ExcType),
	u30(VarName).

instruction(add,1) --> [0xa0].
instruction(add_i,1) --> [0xc5].
instruction(applytype(ArgCount),L+1) --> [0x53], u30(ArgCount,L).
instruction(astype(I),L+1) --> [0x86], u30(I,L).
instruction(astypelate,1) --> [0x87].
instruction(bitand,1) --> [0xa8].
instruction(bitnot,1) --> [0x97].
instruction(bitor,1) --> [0xa9].
instruction(bitxor,1) --> [0xaa].
instruction(call(ArgCount),L+1) --> [0x41], u30(ArgCount,L).
instruction(callmethod(Index,ArgCount),L1+L2+1) --> [0x43], u30(Index,L1), u30(ArgCount,L2).
instruction(callproperty(Index,ArgCount),L1+L2+1) --> [0x46], u30(Index,L1), u30(ArgCount,L2).
instruction(callproplex(Index,ArgCount),L1+L2+1) --> [0x4c], u30(Index,L1), u30(ArgCount,L2).
instruction(callpropvoid(Index,ArgCount),L1+L2+1) --> [0x4f], u30(Index,L1), u30(ArgCount,L2).
instruction(callstatic(Index,ArgCount),L1+L2+1) --> [0x44], u30(Index,L1), u30(ArgCount,L2).
instruction(callsuper(Index,ArgCount),L1+L2+1) --> [0x45], u30(Index,L1), u30(ArgCount,L2).
instruction(callsupervoid(Index,ArgCount),L1+L2+1) --> [0x4e], u30(Index,L1), u30(ArgCount,L2).
instruction(checkfilter,1) --> [0x78].
instruction(coerce(Index),L+1) --> [0x80], u30(Index,L).
instruction(coerce_a,1) --> [0x82].
%TODO: don't know how to handle the following.
%instruction(coerce_i(Index),L+1) --> [0x83], u30(Index,L).
instruction(coerce_s,1) --> [0x85].
instruction(construct(ArgCount),L+1) --> [0x42], u30(ArgCount,L).
instruction(constructprop(Index,ArgCount),L1+L2+1) --> [0x4a], u30(Index,L1), u30(ArgCount,L2).
instruction(constructsuper(ArgCount),L+1) --> [0x49], u30(ArgCount,L).
instruction(convert_b,1) --> [0x76].
instruction(convert_i,1) --> [0x73].
instruction(convert_d,1) --> [0x75].
instruction(convert_o,1) --> [0x77].
instruction(convert_u,1) --> [0x74].
instruction(convert_s,1) --> [0x70].
instruction(debug(DebugType,Index,Reg,Extra),L1+L2+3) --> [0xef], [DebugType], u30(Index,L1), [Reg], u30(Extra,L2).
instruction(debugfile(Index),L+1) --> [0xf1], u30(Index,L).
instruction(debugline(Linenum),L+1) --> [0xf0], u30(Linenum,L).
instruction(declocal(Index),L+1) --> [0x94], u30(Index,L).
instruction(declocal_i(Index),L+1) --> [0xc3], u30(Index,L).
instruction(decrement,1) --> [0x93].
instruction(decrement_i,1) --> [0xc1].
instruction(deleteproperty(Index),L+1) --> [0x6a], u30(Index,L).
instruction(divide,1) --> [0xa3].
instruction(dup,1) --> [0x2a].
instruction(dxns(Index),L+1) --> [0x06], u30(Index,L).
instruction(dxnslate,1) --> [0x07].
instruction(equals,1) --> [0xab].
instruction(esc_xattr,1) --> [0x72].
instruction(esc_xelem,1) --> [0x71].
instruction(findproperty(Index),L+1) --> [0x5e], u30(Index,L).
instruction(findpropstrict(Index),L+1) --> [0x5d], u30(Index,L).
instruction(getdescendants(Index),L+1) --> [0x59], u30(Index,L).
instruction(getglobalscope,1) --> [0x64].
instruction(getglobalslot(SlotIndex),L+1) --> [0x6e], u30(SlotIndex,L).
instruction(getlex(Index),L+1) --> [0x60], u30(Index,L).
instruction(getlocal(Index),L+1) --> [0x62], u30(Index,L).
instruction(getlocal_0,1) --> [0xd0].
instruction(getlocal_1,1) --> [0xd1].
instruction(getlocal_2,1) --> [0xd2].
instruction(getlocal_3,1) --> [0xd3].
instruction(getproperty(Index),L+1) --> [0x66], u30(Index,L).
instruction(getscopeobject(Index),2) --> [0x65], [Index].
instruction(getslot(Index),L+1) --> [0x6c], u30(Index,L).
instruction(getsuper(Index),L+1) --> [0x04], u30(Index,L).
instruction(greaterequals,1) --> [0xb0].
instruction(greaterthan,1) --> [0xaf].
instruction(hasnext,1) --> [0x1f].
instruction(hasnext2(ObjReg,IndexReg),L1+L2+1) --> [0x32], u30(ObjReg,L1), u30(IndexReg,L2).
instruction(ifeq(Offset),4) --> [0x13], s24(Offset).
instruction(iffalse(Offset),4) --> [0x12], s24(Offset).
instruction(ifge(Offset),4) --> [0x18], s24(Offset).
instruction(ifgt(Offset),4) --> [0x17], s24(Offset).
instruction(ifle(Offset),4) --> [0x16], s24(Offset).
instruction(iflt(Offset),4) --> [0x15], s24(Offset).
instruction(ifnge(Offset),4) --> [0x0f], s24(Offset).
instruction(ifngt(Offset),4) --> [0x0e], s24(Offset).
instruction(ifnle(Offset),4) --> [0x0d], s24(Offset).
instruction(ifnlt(Offset),4) --> [0x0c], s24(Offset).
instruction(ifne(Offset),4) --> [0x14], s24(Offset).
instruction(ifstricteq(Offset),4) --> [0x19], s24(Offset).
instruction(ifstrictne(Offset),4) --> [0x1a], s24(Offset).
instruction(iftrue(Offset),4) --> [0x11], s24(Offset).
instruction(in,1) --> [0xb4].
instruction(inclocal(Index),L+1) --> [0x92], u30(Index,L).
instruction(inclocal_i(Index),L+1) --> [0xc2], u30(Index,L).
instruction(increment,1) --> [0x91].
instruction(increment_i,1) --> [0xc0].
instruction(initproperty(Index),L+1) --> [0x68], u30(Index,L).
instruction(instanceof,1) --> [0xb1].
instruction(istype(Index),L+1) --> [0xb2], u30(Index,L).
instruction(istypelate,1) --> [0xb3].
instruction(jump(Offset),4) --> [0x10], s24(Offset).
instruction(kill(Index),L+1) --> [0x08], u30(Index,L).
instruction(label,1) --> [0x09].
instruction(lessequals,1) --> [0xae].
instruction(lessthan,1) --> [0xad].
instruction(lookupswitch(DefaultOffset,CaseCount,CaseOffsets),L) --> 
	[0x1b], 
	s24(DefaultOffset), 
	u30(CaseCount,CL),
	{Count is CaseCount+1},
	case_offsets(CaseOffsets,Count),
	{L is (Count*3)+4+CL}.
instruction(lshift,1) --> [0xa5].
instruction(modulo,1) --> [0xa4].
instruction(multiply,1) --> [0xa2].
instruction(multiply_i,1) --> [0xc7].
instruction(negate,1) --> [0x90].
instruction(negate_i,1) --> [0xc4].
instruction(newactivation,1) --> [0x57].
instruction(newarray(ArgCount),L+1) --> [0x56], u30(ArgCount,L).
instruction(newcatch(Index),L+1) --> [0x5a], u30(Index,L).
instruction(newclass(Index),L+1) --> [0x58], u30(Index,L).
instruction(newfunction(Index),L+1) --> [0x40], u30(Index,L).
instruction(newobject(Index),L+1) --> [0x55], u30(Index,L).
instruction(nextname,1) --> [0x1e].
instruction(nextvalue,1) --> [0x23].
instruction(nop,1) --> [0x02].
instruction(not,1) --> [0x96].
instruction(pop,1) --> [0x29].
instruction(popscope,1) --> [0x1d].
instruction(pushbyte(Byte),2) --> [0x24], [Byte].
instruction(pushdouble(Index),L+1) --> [0x2f], u30(Index,L).
instruction(pushfalse,1) --> [0x27].
instruction(pushint(Index),L+1) --> [0x2d], u30(Index,L).
instruction(pushnamespace(Index),L+1) --> [0x31], u30(Index,L).
instruction(pushnan,1) --> [0x28].
instruction(pushnull,1) --> [0x20].
instruction(pushscope,1) --> [0x30].
instruction(pushshort(Value),L+1) --> [0x25], u30(Value,L).
instruction(pushstring(Index),L+1) --> [0x2c], u30(Index,L).
instruction(pushtrue,1) --> [0x26].
instruction(pushuint(Index),L+1) --> [0x2e], u30(Index,L).
instruction(pushundefined,1) --> [0x21].
%instruction(pushuninitialized(Byte),2) --> [0x22], [Byte].
instruction(pushwith,1) --> [0x1c].
instruction(returnvalue,1) --> [0x48].
instruction(returnvoid,1) --> [0x47].
instruction(rshift,1) --> [0xa6].
instruction(setlocal(Index),L+1) --> [0x63], u30(Index,L).
instruction(setlocal_0,1) --> [0xd4].
instruction(setlocal_1,1) --> [0xd5].
instruction(setlocal_2,1) --> [0xd6].
instruction(setlocal_3,1) --> [0xd7].
instruction(setglobalslot(SlotIndex),L+1) --> [0x6f], u30(SlotIndex,L).
instruction(setproperty(Index),L+1) --> [0x61], u30(Index,L).
instruction(setslot(SlotIndex),L+1) --> [0x6d], u30(SlotIndex,L).
instruction(setsuper(Index),L+1) --> [0x05], u30(Index,L).
instruction(strictequals,1) --> [0xac].
instruction(subtract,1) --> [0xa1].
instruction(subtract_i,1) --> [0xc6].
instruction(swap,1) --> [0x2b].
instruction(throw,1) --> [0x03].
instruction(typeof,1) --> [0x95].
instruction(urshift,1) --> [0xa7].

case_offsets([],0) --> [].
case_offsets([O|R],C) --> {C>0, NC is C-1}, s24(O), case_offsets(R,NC).
